/**
 * @file msc_mScript.c
 *   Some support functions to generate an Octave M script.
 *
 * Copyright (C) 2014 Peter Vranken (mailto:Peter_Vranken@Yahoo.de)
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */
/* Module interface
 *   msc_initModule
 *   msc_shutdownModule
 *   msc_createMScript
 *   msc_cloneByReference
 *   msc_deleteMScript
 *   msc_writeTextBlock
 *   msc_borrowStream
 *   msc_releaseStream
 * Local functions
 *   writeFileHeader
 *   writeFileTrailer
 */

/*
 * Include files
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <assert.h>

#include "smalloc.h"
#include "snprintf.h"
#include "log_logger.h"
#include "lin_linNet.h"
#include "fil_file.h"
#include "msc_mScript.h"


/*
 * Defines
 */


/*
 * Local type definitions
 */


/*
 * Local prototypes
 */


/*
 * Data definitions
 */

/** A global logger object is referenced from anywhere for writing progress messages. */
static log_hLogger_t _log = LOG_HANDLE_TO_EMPTY_LOGGER_OBJECT;

#ifdef  DEBUG
/** A global counter of all references to any created objects. Used to detect memory leaks. */
static unsigned int _noRefsToObjects = 0;
#endif


/*
 * Function implementation
 */

/**
 * Write the header of the M script.
 *   @return
 * The function returns the success. A false indicates a problem, most probable an error in
 * the underlaying stream.
 *   @param pMScript
 * The pointer to the object to operate on.
 */

static boolean writeFileHeader(msc_mScript_t * const pMScript)
{
#define M_FILE_HDR "\
function [tf_%s deviceConstants wBode tiStep] = %s(deviceConstants)\n\
\n\
%%   [tf deviceConstants wBode tiStep] = %s(deviceConstants)\n\
%%                   The transfer function %s of electric linear network\n\
%%                   %s\n\
%%\n\
%%   Input argument(s):\n\
%%       deviceConstants\n\
%%                   The values of the device constants packed into a\n\
%%                   struct. These values are used to create tf. If the\n\
%%                   argument is omitted then the device values from the\n\
%%                   circuit file are used\n\
%%   Return argument(s):\n\
%%       tf          The transfer function object\n\
%%       deviceConstants\n\
%%                   The values of all device constants packed into a\n\
%%                   struct. These are the values, which have been used to\n\
%%                   create tf\n\
%%       wBode       A vector of frequency values (unit: rad/s), which can be\n\
%%                   passed together with tf to a frequency domain function\n\
%%                   like bode or nyquist. The values are derived from a plot\n\
%%                   information in the circuit file. wBode is the empty\n\
%%                   vector if no plot information was specified\n\
%%       tiStep      A vector of time values (unit: s), which can be passed\n\
%%                   together with tf to a time domain function like step or\n\
%%                   impulse. The values are derived from a plot information in\n\
%%                   the circuit file. tiStep is the empty vector if no plot\n\
%%                   information was specified\n\
%%\n\
%%   Example(s):\n\
%%       %s\n\
%%                   Plot the transfer or step function as defined in the\n\
%%                   circuit file\n\
%%       [tf_%s p] = %s\n\
%%                   Get a TF object and the default set of device constants\n\
%%       p.R1 = 2*p.R2\n\
%%                   (Modify the device constants in p)\n\
%%       step(%s(p))\n\
%%                   Plot the step response of the system with modified device\n\
%%                   constants\n\
%%\n\
%%   This file has been generated by " LIN_APP_NAME ", the software for symbolic\n\
%%   analysis of linear electronic circuits.\n\
%%\n\
%%   Copyright (C) %s Peter Vranken (mailto:Peter_Vranken@Yahoo.de)\n\
%%\n\
%%   This program is free software: you can redistribute it and/or modify it\n\
%%   under the terms of the GNU General Public License as published by the\n\
%%   Free Software Foundation, either version 3 of the License, or (at your\n\
%%   option) any later version.\n\
%%\n\
%%   This program is distributed in the hope that it will be useful, but\n\
%%   WITHOUT ANY WARRANTY; without even the implied warranty of\n\
%%   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n\
%%   General Public License for more details.\n\
%%\n\
%%   You should have received a copy of the GNU General Public License along\n\
%%   with this program. If not, see <http://www.gnu.org/licenses/>.\n\
\n\
"
    /* Get current time as string of fixed length. */
    time_t t;
    time(&t);
    const char * const timeStr = ctime(&t);
    char yearStr[5];
    snprintf(yearStr, 5, timeStr+20);

    return fprintf( pMScript->hFile
                  , M_FILE_HDR
                  , pMScript->resultName
                  , pMScript->resultName
                  , pMScript->resultName
                  , pMScript->resultName
                  , pMScript->circuitFileName
                  , pMScript->resultName
                  , pMScript->resultName
                  , pMScript->resultName
                  , pMScript->resultName
                  , yearStr
                  )
           > 0;

#undef M_FILE_HDR
} /* End of writeFileHeader */




/**
 * Write the trailer of the M script.
 *   @return
 * The function returns the success. A false indicates a problem, most probable an error in
 * the underlaying stream.
 *   @param pMScript
 * The pointer to the object to operate on.
 */

static boolean writeFileTrailer(msc_mScript_t * const pMScript)
{
#define M_FILE_TRAILER "\
\n\
end %% of function %s\n\
"
    const char *mfilename = fil_splitPath( /* pPath */ NULL
                                         , /* pPureFileName */ NULL
                                         , /* pExt */ NULL
                                         , pMScript->resultName
                                         );
    return fprintf(pMScript->hFile, M_FILE_TRAILER, mfilename) > 0;

#undef M_FILE_TRAILER
} /* End of writeFileTrailer */




/**
 * Initialize the module at application startup.
 *   @param hLogger
 * This module will use the passed logger object for all reporting during application life
 * time. It must be a real object, LOG_HANDLE_TO_EMPTY_LOGGER_OBJECT is not permitted.
 *   @remark
 * Do not forget to call the counterpart at application end.
 *   @remark
 * This module depends on the other module log_logger. It needs to be initialized after
 * this other module.
 *   @remark Using this function is not an option but a must. You need to call it
 * prior to any other call of this module and prior to accessing any of its global data
 * objects.
 *   @see void msc_shutdownModule()
 */

void msc_initModule(log_hLogger_t hLogger)
{
    assert(hLogger != LOG_HANDLE_TO_EMPTY_LOGGER_OBJECT);
    _log = log_cloneByReference(hLogger);

#ifdef  DEBUG
    /* The DEBUG compilation counts all references to all created objects. */
    _noRefsToObjects = 0;
#endif

#ifdef  DEBUG
    /* Check if patch of snprintf is either not required or properly installed. */
    char buf[3] = {[2] = '\0'};
    snprintf(buf, 2, "%s World", "Hello");
    assert(strlen(buf) == 1);
#endif
} /* End of msc_initModule */




/**
 * Do all cleanup after use of the module, which is required to avoid memory leaks,
 * orphaned handles, etc.
 */

void msc_shutdownModule()
{
#ifdef  DEBUG
    /* The DEBUG compilation looks for still allocated objects in order to detect memory
       leaks. */
    if(_noRefsToObjects != 0)
    {
        fprintf( stderr
               , "msc_shutdownModule: %u references to objects of type"
                 " msc_mScript_t have not been discarded at application"
                 " shutdown. There are probable memory leaks\n"
               , _noRefsToObjects
               );
    }
#endif

    /* Discard reference to the logger object. */
    log_deleteLogger(_log);

} /* End of msc_shutdownModule */




/**
 * Create an object that represents the generated M script.
 *   @return
 * The function returns a Boolean success message. Only if it returns true the created
 * object can be used with the other methods to write the file contents. Most probable
 * reason for a return value false is a file, which could not be opened for write access.\n
 *   In case of errors according messages have been written to the application log.
 *   @param ppMScript
 * The pointer to the newly created object is placed in * \a ppMScript if the function
 * succeeds, otherwise the NULL object (#MSC_NULL_FILE) is returned be reference.
 * A pointer to the new object is returned. Some memory has been allocated on the heap;
 * therefore the object needs to be deleted again after usage.
 *   @param fileName
 * The name of the M script. This is considered a new file. If such a file can't be opened
 * the function fails and a useless NULL object is returned.
 *   @param circuitFileName
 * The name of the circuit definition file, we generate this M script for.
 *   @param resultName
 * This Octave script is related to a single user-defined result from the circuit file. The
 * name of the result is referenced at many locations in the file: as function name and in
 * the documentation.
 *   @see void msc_deleteMScript(msc_mScript_t * const)
 */

boolean msc_createMScript( msc_mScript_t ** const ppMScript
                         , const char * const fileName
                         , const char * const circuitFileName
                         , const char * const resultName
                         )
{
    assert(_log != LOG_HANDLE_TO_EMPTY_LOGGER_OBJECT);

    const boolean doAppend = false;
    assert(fileName != NULL  &&  *fileName != '\000');
    FILE *pFile = fopen(fileName, doAppend? "a": "w");
    if(pFile != NULL)
    {
        msc_mScript_t *pObj = smalloc(sizeof(msc_mScript_t), __FILE__, __LINE__);

        /* The result of the constructor is the first reference to the new object. */
        pObj->noReferencesToThis = 1;

        pObj->fileName = stralloccpy(fileName);
        pObj->circuitFileName = stralloccpy(circuitFileName);
        pObj->resultName = stralloccpy(resultName);
        pObj->hFile = pFile;
#ifdef DEBUG
        pObj->handleBorrowed = false;
#endif
        LOG_DEBUG(_log, "Octave M file %s is opened for write access", fileName);

        *ppMScript = pObj;

#ifdef DEBUG
    ++ _noRefsToObjects;
#endif
        return true;
    }
    else
    {
        LOG_ERROR(_log, "Octave M file %s can't be opened for write access", fileName)
        *ppMScript = MSC_NULL_FILE;

        return false;
    }
} /* End of msc_createMScript */





/**
 * Request another reference to an existing object. The new reference is counted internally
 * for later and safe control of the delete operation.\n
 *   Any requested reference needs to be freed with msc_deleteMScript after use.
 *   @return
 * A copy of the passed pointer \a pExistingObj is returned.
 *   @param pExistingObj
 * Pointer to the object to be cloned by reference.
 *   @see msc_mScript_t *msc_createMScript()
 *   @see msc_deleteMScript(msc_mScript_t * const)
 */

msc_mScript_t *msc_cloneByReference(msc_mScript_t * const pExistingObj)
{
    assert(pExistingObj != NULL);
    ++ pExistingObj->noReferencesToThis;

#ifdef DEBUG
    ++ _noRefsToObjects;
#endif

    return pExistingObj;

} /* End of msc_cloneByReference */





/**
 * Delete a reference to an existing object after use. If there are no references left then
 * the object itself is deleted, all memory is freed.
 *   @param pMScript
 * Pointer to the object to be deleted. No action if this is the null object #MSC_NULL_FILE.
 *   @see msc_mScript_t *msc_createMScript(const char * const fileName)
 *   @see msc_mScript_t *msc_cloneByReference(msc_mScript_t *)
 */

void msc_deleteMScript(msc_mScript_t * const pMScript)
{
    if(pMScript == MSC_NULL_FILE)
        return;

    /* Deletion takes place only if there are no known other references to this object. */
    assert(pMScript->noReferencesToThis >= 1);
    if(--pMScript->noReferencesToThis == 0)
    {
        if(pMScript->hFile != NULL)
        {
            if(fclose(pMScript->hFile) != 0)
            {
                LOG_ERROR( _log
                         , "Error while closing file %s. The file contents are possibly"
                           " corrupt"
                         , pMScript->fileName
                         )
            }
            else
                LOG_INFO(_log, "Octave file %s successfully written", pMScript->fileName)
        }
        else
            assert(false);

        free((char*)pMScript->fileName);
        free((char*)pMScript->circuitFileName);
        free((char*)pMScript->resultName);
        free(pMScript);
    }

#ifdef DEBUG
    -- _noRefsToObjects;
#endif
} /* End of msc_deleteMScript */





/**
 * Write a predefined text block into the generated M file. Such a text block can e.g. be
 * the file header or a blank line. See enumeration msc_enumTextBlock_t.
 *   @return
 * The function returns the success. A false indicates a problem, most probable an error in
 * the underlaying stream.
 *   @param pMScript
 * The pointer to the object to operate on.
 *   @param kindOfTextBlock
 * Which predefined text block to write?
 */

boolean msc_writeTextBlock( msc_mScript_t * const pMScript
                          , msc_enumTextBlock_t kindOfTextBlock
                          )
{
    assert(pMScript != NULL  &&  !pMScript->handleBorrowed);

    boolean success;
    switch(kindOfTextBlock)
    {
    case msc_txtBlkHeader:
        success = writeFileHeader(pMScript);
        break;

    case msc_txtBlkTrailer:
        success = writeFileTrailer(pMScript);
        break;

    case msc_txtBlkBlankLine:
        success = fputs("\n", pMScript->hFile) >= 0;
        break;

    case msc_txtBlkAddPath:
        success = fputs("addpath " "linNet_private" ";\n", pMScript->hFile) >= 0;
        break;

    case msc_txtBlkLoadPkgs:
        success = fputs( "% Load the required Octave packages.\n"
                         "if isOctave\n"
                         "    pkg load control\n"
                         "end\n"
                         "\n"
                       , pMScript->hFile
                       ) >= 0;
        break;
        
    default:
        assert(false);
        success = false;
    }

    if(!success)
    {
        LOG_FATAL( _log
                 , "Could not write to generated M script %s"
                 , pMScript->fileName
                 );
    }

    return success;

} /* End of msc_writeTextBlock */




/**
 * Borrow the stream from the object. The returned stream object is a standard stream,
 * which can eb used with all the output functions of the library stdio. The output methods
 * of this class must not be used while the stream object is borrowed.\n
 *   Two clients must not borrow the stream object at the same time. This holds still true
 * if the different clients have different clones (by reference) of the same object.
 *   @return
 * Get the handle of the stream associated with this M script object.
 *   @param pMScript
 * The pointer to the object, whose stream is borrowed for external output.
 *   @see void releaseStream(msc_mScript_t * const pMScript)
 */

FILE *msc_borrowStream(msc_mScript_t * const pMScript)
{
    assert(pMScript != NULL  &&  !pMScript->handleBorrowed);
    fflush(pMScript->hFile);
#ifdef DEBUG
    pMScript->handleBorrowed = true;
#endif
    return pMScript->hFile;

} /* End of msc_borrowStream */





/**
 * Return a borrowed stream object or indicate that it is no longer used.
 *   @param pMScript
 * The pointer to the object, whose stream is returned.
 *   @remark
 * The function FILE *msc_borrowStream(msc_mScript_t * const) needs to be called again
 * prior to using output function of the library stdio again.
 */

void msc_releaseStream(msc_mScript_t * const pMScript)
{
    assert(pMScript != NULL  &&  pMScript->handleBorrowed);
#ifdef DEBUG
    pMScript->handleBorrowed = false;
#endif
    fflush(pMScript->hFile);

} /* End of msc_releaseStream */



